﻿/*
 * 
 * Description: Office COMAddins Connector main dlg
 */
#include "pch.h"
#include "framework.h"
#include "office_comaddin_connector.h"
#include "office_comaddin_connector_dlg.h"
#include "afxdialogex.h"

#include <thread>
#include <chrono>
#include "oleacc.h"
#pragma comment(lib, "Oleacc.lib")
#include "CCOMAddIn.h"
#include "CCOMAddIns.h"
#include "CExcelApplication.h"
#include "CExcelWindows.h"
#include "CPptApplication.h"
#include "CWordApplication.h"
#include "CWordWindows.h"

using namespace CONSTANT;

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

class CAboutDlg : public CDialogEx {
public:
    CAboutDlg();

// 对话框数据
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_ABOUTBOX };
#endif

protected:
    virtual void DoDataExchange(CDataExchange *pDX);  // DDX/DDV 支持

    // 实现
protected:
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX) {}

void CAboutDlg::DoDataExchange(CDataExchange *pDX)
{
    CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()

// OfficeCOMAddinConnectorDlg 对话框

OfficeCOMAddinConnectorDlg::OfficeCOMAddinConnectorDlg(CWnd *pParent /*=nullptr*/)
    : CDialogEx(IDD_OCCONNECTOR_DIALOG, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void OfficeCOMAddinConnectorDlg::DoDataExchange(CDataExchange *pDX)
{
    CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(OfficeCOMAddinConnectorDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON_CONNECT_COMADDIN, &OfficeCOMAddinConnectorDlg::OnBnClickedButtonConnectComaddin)
END_MESSAGE_MAP()

// OfficeCOMAddinConnectorDlg 消息处理程序

BOOL OfficeCOMAddinConnectorDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    // 隐藏MFC窗口
    ModifyStyleEx(WS_EX_APPWINDOW, WS_EX_TOOLWINDOW);
    WINDOWPLACEMENT wp;
    wp.length = sizeof WINDOWPLACEMENT;
    wp.flags = WPF_RESTORETOMAXIMIZED;
    wp.showCmd = SW_HIDE;
    SetWindowPlacement(&wp);

    if (!GetArguments(appName_, comAddinName_)) {
        PostQuitMessage(-1);
    }

    CButton *pButton = (CButton *)GetDlgItem(IDC_BUTTON_CONNECT_COMADDIN);
    if (pButton != nullptr) {
        // trigger button click event to invoking OfficeCOMAddinConnectorDlg::OnBnClickedButtonConnectComaddin fucntion
        pButton->SendMessage(WM_LBUTTONDOWN);
        Sleep(200);
        pButton->SendMessage(WM_LBUTTONUP);
    }
    PostQuitMessage(0);
    return TRUE;
}

void OfficeCOMAddinConnectorDlg::OnSysCommand(UINT nID, LPARAM lParam) {}

void OfficeCOMAddinConnectorDlg::OnPaint() {}

HCURSOR OfficeCOMAddinConnectorDlg::OnQueryDragIcon()
{
    return static_cast<HCURSOR>(m_hIcon);
}

namespace {
HWND hWwG = nullptr;

BOOL CALLBACK EnumChildProc(HWND hChild, LPARAM lParam)
{
    if ((void *)lParam == nullptr) {
        return false;
    }
    char clsName[MAX_PATH] = { 0 };
    GetClassName(hChild, clsName, MAX_PATH);
    if (!strcmp((char *)lParam, clsName)) {
        hWwG = hChild;
    }
    return true;
}

}

bool OfficeCOMAddinConnectorDlg::WaitForDocWindow(const char *mainWndText, const char *docWndText,
                                                  CComPtr<IDispatch> &spDisp)
{
    if (mainWndText == nullptr || docWndText == nullptr) {
        return false;
    }
    HRESULT res = E_FAIL;
    uint64_t beginMilli =
        std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
            .count();
    do {
        HWND hWord = ::FindWindow(mainWndText, nullptr);
        if (hWord == nullptr) {
            continue;
        }
        EnumChildWindows(hWord, EnumChildProc, (LPARAM)docWndText);
        while (hWwG == nullptr) {
            EnumChildWindows(hWord, EnumChildProc, (LPARAM)docWndText);
            hWord = ::GetNextWindow(hWord, GW_HWNDNEXT);
            if (hWord == nullptr) {
                break;
            }
        }
        if (hWwG == NULL) {
            Sleep(TIME_UNIT_MILLI_PER_SECOND);
            continue;
        }

        res = AccessibleObjectFromWindow(hWwG, OBJID_NATIVEOM, IID_IDispatch, (void **)&spDisp);
        if (res != S_OK) {
            uint64_t crtMilli = std::chrono::duration_cast<std::chrono::milliseconds>(
                                    std::chrono::system_clock::now().time_since_epoch())
                                    .count();
            if (crtMilli - beginMilli >= MAX_WAIT_MILLI_SECONDS) {
                return false;
            }
            Sleep(TIME_UNIT_MILLI_PER_SECOND);
        }
    } while (res != S_OK);
    return true;
}

void OfficeCOMAddinConnectorDlg::ConnectCOMAddIn(CCOMAddIns &addins, const std::string &comAddinName)
{
    size_t count = addins.get_Count();
    for (size_t i = 1; i <= count; i++) {
        CCOMAddIn addin = addins.Item(COleVariant(i));
        CString name = addin.get_ProgId();
        if (name == comAddinName.c_str()) {
            if (!addin.get_Connect()) {
                addin.put_Connect(true);
            }
        }
        addin.ReleaseDispatch();
    }
}

void OfficeCOMAddinConnectorDlg::ConnectWordCOMAddIn()
{
    CWordApplication wordApp;
    CComPtr<IDispatch> spDisp;
    CWordWindows window;
    if (!WaitForDocWindow(TEXT_WORD_MAIN_WND, TEXT_WORD_WINDOW_WND, spDisp)) {
        return;
    }
    window.AttachDispatch(spDisp);
    wordApp.AttachDispatch(window.get_Application());

    CCOMAddIns addins = wordApp.get_COMAddIns();
    ConnectCOMAddIn(addins, comAddinName_);
    window.ReleaseDispatch();
    addins.ReleaseDispatch();
    wordApp.ReleaseDispatch();
}

void OfficeCOMAddinConnectorDlg::ConnectExcelCOMAddIn()
{
    CExcelApplication excelApp;
    CComPtr<IDispatch> spDisp;
    CExcelWindows window;
    if (!WaitForDocWindow(TEXT_EXCEL_MAIN_WND, TEXT_EXCEL_WINDOW_WND, spDisp)) {
        return;
    }
    window.AttachDispatch(spDisp);
    excelApp.AttachDispatch(window.get_Application());

    CCOMAddIns addins = excelApp.get_COMAddIns();
    ConnectCOMAddIn(addins, comAddinName_);
    window.ReleaseDispatch();
    addins.ReleaseDispatch();
    excelApp.ReleaseDispatch();
}

void OfficeCOMAddinConnectorDlg::ConnectPowerPointCOMAddIn()
{
    CPptApplication pptApp;
    pptApp.CreateDispatch(TEXT_POWERPOINT_PROGID, nullptr);

    CCOMAddIns addins = pptApp.get_COMAddIns();
    ConnectCOMAddIn(addins, comAddinName_);
    addins.ReleaseDispatch();
    pptApp.ReleaseDispatch();
}

bool OfficeCOMAddinConnectorDlg::SplitLastArgument(std::string &cmdline, std::string &lastArgument)
{
    size_t idx = cmdline.find_last_of(" ");
    if (idx == std::string::npos || idx >= cmdline.size()) {
        return false;
    }
    size_t count = cmdline.size() - idx;
    std::string arg = cmdline.substr(idx + 1, count);
    cmdline = cmdline.substr(0, cmdline.size() - count);
    lastArgument = arg;
    return true;
}

bool OfficeCOMAddinConnectorDlg::GetArguments(std::string &appName, std::string &comAddinName)
{
    std::string cmdline = GetCommandLine();
    if (!(SplitLastArgument(cmdline, comAddinName) && SplitLastArgument(cmdline, appName))) {
        return false;
    }
    if (SUPPORTED_ARGS.find(appName) == SUPPORTED_ARGS.end()) {
        return false;
    }
    return true;
}

void OfficeCOMAddinConnectorDlg::OnBnClickedButtonConnectComaddin()
{
    if (appName_ == TEXT_DEFAULT) {
        AfxMessageBox("wrong argument! supported argument: 1. one of [Word, Excel, PowerPoint], 2. comaddin ProgId");
        return;
    } else if (appName_ == TEXT_WORD) {
        ConnectWordCOMAddIn();
    } else if (appName_ == TEXT_EXCEL) {
        ConnectExcelCOMAddIn();
    } else if (appName_ == TEXT_POWERPOINT) {
        ConnectPowerPointCOMAddIn();
    }
}
